/* global Exhibit */ 
Exhibit.SliderFacet.slider = function(div, facet, precision) {
    this._div = div;
    this._facet = facet;
    this._prec = precision || 0.1;
    this._maxRange = {min: parseFloat(Exhibit.Util.round(facet._maxRange.min-precision/2, this._prec)),  // round down
                   max: parseFloat(Exhibit.Util.round(facet._maxRange.max+precision/2, this._prec))}; // round up
    this._horizontal = this._facet._settings.horizontal;

    this._scaleFactor = null; // scale factor used for determining value from slider position
    this._slider1 = {};
    this._slider2 = {};

    this._dom = Exhibit.jQuery.simileDOM(
        "string",
        div,
        '<div class="exhibit-slider-bar" id="bar">' +
            '<div id="slider1"></div>' +
            '<div id="slider2"></div>' +
            (this._facet._settings.histogram? '<div class="exhibit-slider-histogram" id="histogram"></div>':'') +
        '</div>' +
        '<div class="exhibit-slider-display">' +
        (this._facet._settings.inputText? '<input type="text" id="minDisplay"></input> - <input type="text" id="maxDisplay"></input> '
              : '<span id="minDisplay"></span> - <span id="maxDisplay"></span>')  +
        '</div>'
    );

    // sizing
    var horizontal = this._horizontal;
    var histogram = this._dom.histogram;

    if (horizontal && histogram) {             // horizontal and histogram
        this._dom.bar.css({height: '14px',
                           width: '150px'});
    }
    else if (horizontal && !histogram) {       // horizontal and no histogram
        this._dom.bar.css({height: '1px',
                           width: '150px'});
    }
    else if (!horizontal && histogram) {      // vertical and histogram
        this._dom.bar.css({left: '14px', height: '150px',
                           width: '14px'});
    }
    else {                                    // vertical and no histogram
        this._dom.bar.css({left: '14px', height: '150px',
                           width: '1px'});
    }

    if (this._facet._settings.height) {
        this._dom.bar.css("height", this._facet._settings.height);
    }
    if (this._facet._settings.width) {
        this._dom.bar.css("width", this._facet._settings.width);
    }
        
    if (histogram) {
        this._dom.histogram.css({height: "100%", width: "100%"});
    }

    this._scaleFactor = (this._maxRange.max - this._maxRange.min) /
        this._dom.bar[0][horizontal? "offsetWidth" : "offsetHeight"];

    // init sliders
    this._slider1 = new Exhibit.SliderFacet.slider.slider(this._dom.slider1[0], this);
    this._slider2 = new Exhibit.SliderFacet.slider.slider(this._dom.slider2[0], this);
    this._setSlider(this._slider1, this._maxRange.min);
    this._setSlider(this._slider2, this._maxRange.max);

    this._registerDragging();
    

    if (this._facet._settings.inputText) {
        this._registerInputs();
    }
    
};

// If you call this, it's up to you to notifyFacet if necessary
Exhibit.SliderFacet.slider.prototype.resetSliders = function(){ 
    this._setSlider(this._slider1, this._maxRange.min);
    this._setSlider(this._slider2, this._maxRange.max);
};

// If you call this, it's up to you to notifyFacet if necessary
Exhibit.SliderFacet.slider.prototype._setSlider = function(slider, value) {
    if (value > this._maxRange.max) {
        value = this._maxRange.max;
    }
    else if (value < this._maxRange.min) {
        value = this._maxRange.min;
    }

    value = parseFloat(Exhibit.Util.round(value, this._prec));

    slider.value = value;
    
    slider.div.style[this._horizontal ? "left" : "top"] =
            ((value-this._maxRange.min)/this._scaleFactor-slider.offset) + 'px';
    this._setDisplays(slider);
};

// If you call this, it's up to you to notifyFacet if necessary
Exhibit.SliderFacet.slider.prototype._setMin = function(value) {
    var slider = this._slider1.value < this._slider2.value? this._slider1 : this._slider2;
    var other = (slider == this._slider1)? this._slider2 : this._slider1;

    value = parseFloat(value);
    if (isNaN(value)) {
        return;
    }

    if (value > other.value) {
        value = other.value;
    }

    this._setSlider(slider, value);
};

// If you call this, it's up to you to notifyFacet if necessary
Exhibit.SliderFacet.slider.prototype._setMax = function(value) {
    var slider = this._slider1.value > this._slider2.value? this._slider1 : this._slider2;
    var other = (slider == this._slider1)? this._slider2 : this._slider1;

    value = parseFloat(value);
    if (isNaN(value)) {
        return;
    }

    if (value < other.value) {
        value = other.value;
    }

    this._setSlider(slider, value);
};

// Updates displays based on slider's value (i.e., slider's value should have changed recently)
Exhibit.SliderFacet.slider.prototype._setDisplays = function(slider) {
    var other = (slider == this._slider1)? this._slider2 : this._slider1;

    var min = Math.min(slider.value, other.value);
    var max = Math.max(slider.value, other.value);

    if (this._facet._settings.inputText) {
        this._dom.minDisplay.val(min);
        this._dom.maxDisplay.val(max);
    } else {
        this._dom.minDisplay.html(min);
        this._dom.maxDisplay.html(max);
    }
};

Exhibit.SliderFacet.slider.slider = function(div, self) { // individual slider handle that can be dragged
    var barEl = self._dom.bar[0];

    this.div = div; // containing div of handle

    if (self._horizontal) {
        this.div.className = "exhibit-slider-handle"; 
        this.div.style.backgroundImage = 'url("'+Exhibit.urlPrefix+'images/slider-handle.png")';
        this.offset = this.div.offsetWidth/2;
        this.min = -this.offset; // slider's middle can reach left edge of bar
        this.max = barEl.offsetWidth - this.offset; //slider's middle can reach right edge of bar
    } else {
        this.div.className = "exhibit-slider-handle2"; 
        this.div.style.backgroundImage = 'url("'+Exhibit.urlPrefix+'images/slider-handle2.png")';
        this.offset = this.div.offsetHeight/2;
        this.min = -this.offset; // slider's middle can reach left edge of bar
        this.max = barEl.offsetHeight - this.offset; //slider's middle can reach right edge of bar
    }

    if (self._horizontal) {
        if (self._facet._settings.histogram) {
            this.div.style.top = (barEl.offsetHeight-4)+'px';
        }
    } else {
        this.div.style.left = '-16px';
    }
};


Exhibit.SliderFacet.slider.prototype._registerDragging = function() {
    var self = this;

    var startDrag = function(slider) {
        return function(e) {
            e = e || window.event;
            
            var onMove = self._horizontal? onDragH(e, slider) : onDragV(e, slider);
            
            if (document.attachEvent) {
                document.attachEvent('onmousemove', onMove);
                document.attachEvent('onmouseup', endDrag(slider, onMove));
            } else {
                document.addEventListener('mousemove', onMove, false);
                document.addEventListener('mouseup', endDrag(slider, onMove), false);
            }

            e.preventDefault();
            return false;
        };
    };

    var onDragH = function(e, slider) {
        var origX = e.screenX;
        var origLeft = parseInt(slider.div.style.left);
        var min = slider.min;
        var max = slider.max;

        return function(e) {
            e = e || window.event;

            var dx = e.screenX - origX;
            var newLeft = origLeft + dx;
            if (newLeft < min) {
                newLeft = min;
            }
            if (newLeft > max) {
                newLeft = max;
            }
            slider.div.style.left = newLeft + 'px';

            //setTimeout keeps setDisplay from slowing down the dragging
            //I'm not entirely sure why, but I think it might have something to do with it putting the call in a new environment
            setTimeout(function(){ var position = parseInt(slider.div.style.left) + slider.offset;
                                   slider.value = parseFloat(Exhibit.Util.round(position*self._scaleFactor+self._maxRange.min, self._prec));
                                   self._setDisplays(slider); }, 0);
        };
    };

    var onDragV = function(e, slider) {
        var origY = e.screenY;
        var origTop = parseInt(slider.div.style.top);
        var min = slider.min;
        var max = slider.max;

        return function(e) {
            e = e || window.event;

            var dy = e.screenY - origY;
            var newTop = origTop + dy;
            if (newTop < min) {
                newTop = min;
            }
            if (newTop > max) {
                newTop = max;
            }
            slider.div.style.top = newTop + 'px';

            //setTimeout keeps setDisplay from slowing down the dragging
            //I'm not entirely sure why, but I think it might have something to do with it putting the call in a new environment
            setTimeout(function(){ var position = parseInt(slider.div.style.top) + slider.offset;
                                   slider.value = parseFloat(Exhibit.Util.round(position*self._scaleFactor+self._maxRange.min, self._prec));
                                   self._setDisplays(slider); }, 0);
        };
    };

    var endDrag = function(slider, moveListener) {
        return function(e) {
            
            if(document.detachEvent) {
                document.detachEvent('onmousemove', moveListener);
                document.detachEvent('onmouseup', arguments.callee);
            } else {
                document.removeEventListener('mousemove', moveListener, false);
                document.removeEventListener('mouseup', arguments.callee, false); //remove this function
            }

            self._notifyFacet();
        };
    };

    var attachListeners = function(slider) {
        if (document.attachEvent) {
            slider.div.attachEvent('onmousedown', startDrag(slider));
        } else {
            slider.div.addEventListener('mousedown', startDrag(slider), false);
        }
    };

    attachListeners(this._slider1);
    attachListeners(this._slider2);
        
};


Exhibit.SliderFacet.slider.prototype._notifyFacet = function() {
    var val1 = this._slider1.value;
    var val2 = this._slider2.value;
    this._facet.changeRange( {min: Math.min(val1, val2), max: Math.max(val1, val2)} );
};


Exhibit.SliderFacet.slider.prototype.updateHistogram = function(data) {
    // data ([numbers]): the values to graphed (essentially specifies the relative height of each bar)
    // data.length = # of bars
    
    var n = data.length;
    var histogram = this._dom.histogram[0]; //data = [0,1,2]; n = 4;

    var maxVal = Math.max.apply(Math, data); //find the max of an array
    if (!maxVal) {
        return; //nothing to draw
    }
    var width, maxHeight, ratio, height, bar;

    if (this._horizontal) {
        width = histogram.offsetWidth/n;  // width of each bar
        maxHeight = histogram.offsetHeight;
        ratio = maxHeight/maxVal;
        
        histogram.innerHTML = ''; //clear histogram
        
        for (var i=0; i<n; i++){ // create new bars
            height = Math.ceil(data[i]*ratio); //ceil instead of round
            //ensures any nonzero bar will show something
            
            bar = document.createElement('div');
            histogram.appendChild(bar);
            bar.style.width = width+'px';
            bar.style.height = height+'px';
            bar.style.position = 'absolute';
            bar.style.top = (maxHeight-height)+'px';
            bar.style.left = i*width+'px';

            bar.style.display = height? '' : 'none'; //IE, even with font-size:0, 
                                                     //will still render divs with height:0
                                                     //as several pixels tall
        }
    } else {                                   // vertical (height and width are essentially flipped)
        width = histogram.offsetHeight/n;  // width of each bar
        maxHeight = histogram.offsetWidth;
        ratio = maxHeight/maxVal;
        
        histogram.innerHTML = ''; //clear histogram
        
        for (var i=0; i<n; i++){ // create new bars
            height = Math.ceil(data[i]*ratio);
            
            bar = document.createElement('div');
            histogram.appendChild(bar);
            bar.style.height = width+'px';
            bar.style.width = height+'px';
            bar.style.position = 'absolute';
            bar.style.left = '0px';
            bar.style.top = i*width+'px';
        }
    }

};

Exhibit.SliderFacet.slider.prototype._registerInputs = function() {
    var self = this;

    this._dom.minDisplay.change(function(e) {
        self._setMin(this.value);
        self._notifyFacet();
    });
    this._dom.maxDisplay.change(function(e) {
        self._setMax(this.value);
        self._notifyFacet();
    });
};
